﻿using System;
using Aeon.Framework.Audio;
using Aeon.Framework.Audio.Fmod;
using System.Collections.Generic;

namespace Aeon.Framework
{

/// <summary>
/// Audio manager module class.
/// Implemented for FMOD.
/// 
/// [TODO] delta time engine.update for sync?
/// 
/// </summary>
public sealed class AeonAudioManager : IAudioManager
    {
    // --- Inner Types:

    /// <summary>
    /// Core data for audio channels.
    /// 
    /// </summary>
    private class AudioCore
        {
        // --- Attributes:

        private ChannelStatus status;       // Index value for the channel.
        private Channel channel;            // Voice slot in the engine.
        private Boolean playing;            // Use flag.

        private Int32 key;                  // Unique identifier.
        private static Int32 counterID = 0; // Unique key generator counter.

        // --- Properties:

        public Boolean Allocated
            {
            get {
                if (status == ChannelStatus.FREE) return(false);
                else return(true);
                }
            }

        public Channel Channel 
            {
            get { return(channel); }
            set { channel = value; }        // To re-set the reference altered channel.
            }

        public Int32 Key { get { return(key); } }
        public Boolean Playing { get { return(playing); } }
        public ChannelStatus Status { get { return(status); } }

        // --- Constructor:

        public AudioCore()
            {
            playing = false;
            channel = null;
            status = ChannelStatus.FREE;

            key = counterID;
            counterID++;
            }

        // --- Public Methods:

        public void Lock()
            {
            playing = true;
            status = ChannelStatus.REUSE;
            }

        public void Unlock() { playing = false; }

        }

    // --- Fields:

    private Engine engine;              // FMOD system engine.
    private Boolean muted;              // Output volume switch.

    private List<AudioCore> channels;   // Channel collection.

    private Result status;              // Status code for the last operation.

    // --- Constructor:

    public AeonAudioManager()
        {
        muted = false;

        dispose();
        initialize();
        }

    // --- Internal Methods:

    private void initialize()
        {
        status = Factory.System_Create(ref engine);
        // getVersion?

        status = engine.init(32,INITFLAGS.NORMAL,(IntPtr)null);
        }

    private void dispose()
        {
        if (engine != null)
            {
            status = engine.close();
            status = engine.release();

            engine = null;            
            }

        channels = new List<AudioCore>();
        for (int i = 0; i <8; i++) { channels.Add(new AudioCore()); }
        }

    private MODE toBitfield(AudioMode m)
            {
            MODE pos = (m.Relative) ? (MODE._3D_HEADRELATIVE):(MODE._3D_WORLDRELATIVE);
            MODE acc = (m.Software) ? (MODE.SOFTWARE):(MODE.HARDWARE);
            MODE loo = (m.Loop) ? (MODE.LOOP_NORMAL):(MODE.LOOP_OFF);
            MODE dim = (m.D2) ? (MODE._2D):(MODE._3D);

            return(pos|acc|loo|dim);
            }

    private int genChannel()
        {
        foreach(AudioCore c in channels) { if (!c.Playing) return(c.Key); }

        AudioCore extra = new AudioCore();
        channels.Add(extra);
        return(extra.Key);
        }

    private AudioCore getCore(int key)
        {
        foreach(AudioCore c in channels) { if (c.Key == key) return(c); }

        return(null);
        }

    // --- External Methods:

    /// <summary>
    /// Restores manager to initial uninitialized state releasing all resources.
    /// 
    /// </summary>
    public void CleanSounds()
        {
        
        dispose();
        }

    /// <summary>
    /// Starts playing sound.
    /// 
    /// </summary>
    public void Play(IAudio audio)
        {
        if (audio.Channel < 0) audio.Channel = genChannel();

        AudioCore core = getCore(audio.Channel);
        Channel chan = core.Channel;

        Sound sound = null;
        status = engine.createSound(audio.Source, toBitfield(audio.Settings) | MODE.CREATESAMPLE, ref sound);
        status = engine.playSound(core.Status, sound, true, ref chan);
        core.Channel = chan;

        if (muted) chan.setVolume(0);
        else chan.setVolume(audio.Volume);

        chan.setPaused(false);
        core.Lock();
        }

    /// <summary>
    /// Pauses or resumes playing a sound.
    /// 
    /// </summary>
    public void Pause(IAudio audio)
        {
        AudioCore core = getCore(audio.Channel);
        Channel chan = core.Channel;

        if (core.Playing) { chan.setPaused(true); core.Unlock(); }
        else { chan.setPaused(false); core.Lock(); }
        }

    /// <summary>
    /// Stops playing a sound.
    /// 
    /// </summary>
    /// <param name="audio"></param>
    public void Stop(IAudio audio)
        {
        AudioCore core = getCore(audio.Channel);
        Channel chan = core.Channel;

        chan.stop();
        core.Unlock();
        }

    /// <summary>
    /// Updates playing volume.
    /// Ugly kludge...
    /// 
    /// </summary>
    public void ChangeVolume(IAudio audio)
        {
        AudioCore core = getCore(audio.Channel);

        if (core.Playing)
            {
            Channel chan = core.Channel;
            chan.setVolume(audio.Volume);
            }
        }

    }


}
